﻿//-----------------------------------------------------------------------
// <copyright>
// Copyright (C) Sergey Solyanik for The Black Square Project.
//
// This file is subject to the terms and conditions of the Microsoft Public License (MS-PL).
// See http://www.microsoft.com/opensource/licenses.mspx#Ms-PL for more details.
// </copyright>
//----------------------------------------------------------------------- 
using System.Collections.Generic;

using DocumentFormat.OpenXml;
using DocumentFormat.OpenXml.Packaging;
using DocumentFormat.OpenXml.Wordprocessing;

namespace DocumentManager
{
    /// <summary>
    /// Various useful utilities for the document files.
    /// </summary>
    public static class DocumentUtils
    {
        /// <summary>
        /// Merges comments from two doc files.
        /// </summary>
        /// <param name="toDocument"> First file. </param>
        /// <param name="fromDocument"> Second file. </param>
        public static void MergeComments(string toDocument,
            string fromDocument)
        {
            WordprocessingDocument toDocumentWP = WordprocessingDocument.Open(toDocument, true);
            WordprocessingDocument fromDocumentWP = WordprocessingDocument.Open(fromDocument, true);
            try
            {
                CommentMerger.MergeComments(toDocumentWP, fromDocumentWP);
            }
            finally
            {
                toDocumentWP.Close();
                fromDocumentWP.Close();
            }   
        }

        /// <summary>
        /// Removes duplicate comment in an OpenXML document.
        /// </summary>
        /// <param name="documentName"> Word document name. </param>
        public static void RemoveDuplicateComments(string documentName)
        {
            Dictionary<int, Comment> comments = new Dictionary<int, Comment>();
            Dictionary<int, string> commentTexts = new Dictionary<int, string>();
            Dictionary<int, CommentRangeStart> commentRangeStarts = new Dictionary<int, CommentRangeStart>();
            Dictionary<int, CommentRangeEnd> commentRangeEnds = new Dictionary<int, CommentRangeEnd>();
            Dictionary<int, OpenXmlElement> commentReferenceParents = new Dictionary<int, OpenXmlElement>();
            HashSet<OpenXmlElement> commentReferenceParentsSet = new HashSet<OpenXmlElement>();
            HashSet<int> idsOfIdenticalStarts = new HashSet<int>();
            HashSet<int> idsOfIdenticalEnds = new HashSet<int>();

            WordprocessingDocument doc = WordprocessingDocument.Open(documentName, true);
            foreach (Comment c in doc.MainDocumentPart.WordprocessingCommentsPart.Comments)
            {
                int id = int.Parse(c.Id);
                comments.Add(id, c);
                commentTexts.Add(id, c.Author + " : " + c.InnerText);
            }

            foreach (CommentReference cRef in doc.MainDocumentPart.RootElement.Descendants<CommentReference>())
            {
                commentReferenceParents.Add(int.Parse(cRef.Id), cRef.Parent);
                commentReferenceParentsSet.Add(cRef.Parent);
            }

            foreach (CommentRangeStart baseRs in doc.MainDocumentPart.RootElement.Descendants<CommentRangeStart>())
            {

                int baseId = int.Parse(baseRs.Id);

                if (commentRangeStarts.ContainsKey(baseId))
                    continue;

                commentRangeStarts[baseId] = baseRs;

                string baseCommentText = commentTexts[baseId];

                OpenXmlElement el = baseRs;
                for (; ; )
                {
                    el = el.NextSibling();
                    if (el == null || !string.IsNullOrEmpty(el.InnerText))
                        break;

                    CommentRangeStart rs = el as CommentRangeStart;
                    if (rs == null)
                        continue;

                    int rsId = int.Parse(rs.Id);
                    if (baseCommentText == commentTexts[rsId])
                    {
                        commentRangeStarts[rsId] = rs;

                        // This preserves the smallest comment by the id,
                        // ensuring that the duplicate comments that have
                        // been merged in are removed.
                        if (baseId < rsId)
                        {
                            idsOfIdenticalStarts.Add(rsId);
                        }
                        else
                        {
                            idsOfIdenticalStarts.Add(baseId);
                            baseId = rsId;
                        }
                    }
                }
            }

            foreach (CommentRangeEnd baseRe in doc.MainDocumentPart.RootElement.Descendants<CommentRangeEnd>())
            {
                int baseId = int.Parse(baseRe.Id);

                if (commentRangeEnds.ContainsKey(baseId))
                    continue;

                commentRangeEnds[baseId] = baseRe;

                string baseCommentText = commentTexts[baseId];

                OpenXmlElement el = baseRe;
                for (; ; )
                {
                    el = el.NextSibling();
                    if (el == null || !string.IsNullOrEmpty(el.InnerText))
                        break;

                    CommentRangeEnd re = el as CommentRangeEnd;
                    if (re == null)
                        continue;

                    int reId = int.Parse(re.Id);
                    if (baseCommentText == commentTexts[reId])
                    {
                        commentRangeEnds[reId] = re;

                        // This preserves the smallest comment by the id,
                        // ensuring that the duplicate comments that have
                        // been merged in are removed.
                        if (baseId < reId)
                        {
                            idsOfIdenticalEnds.Add(reId);
                        }
                        else
                        {
                            idsOfIdenticalEnds.Add(baseId);
                            baseId = reId;
                        }
                    }
                }
            }

            foreach (int id in idsOfIdenticalStarts)
            {
                if (idsOfIdenticalEnds.Contains(id))
                {
                    commentRangeStarts[id].Remove();
                    commentRangeEnds[id].Remove();
                    commentReferenceParents[id].Remove();
                    comments[id].Remove();
                }
            }

            doc.MainDocumentPart.RootElement.Save();
            doc.MainDocumentPart.WordprocessingCommentsPart.RootElement.Save();

            doc.Close();
        }
    }
}